Skip to content

Feat/persist resources (WIP)#3553

Merged
TheodoreSpeaks merged 9 commits intofeat/mothership-copilotfrom
feat/persist-resources
Mar 13, 2026
Merged

Feat/persist resources (WIP)#3553
TheodoreSpeaks merged 9 commits intofeat/mothership-copilotfrom
feat/persist-resources

Conversation

@TheodoreSpeaks
Copy link
Collaborator

Summary

Allows people to create and delete resources.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

How has this been tested? What should reviewers focus on?

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 13, 2026 2:31am

Request Review

@cursor
Copy link

cursor bot commented Mar 13, 2026

PR Summary

Medium Risk
Adds a new persisted resources JSONB column on copilot_chats plus new API/mutation paths that update it, and wires tool execution to emit and store resources; mistakes could lead to incorrect resource lists or ordering in tasks. Changes touch both orchestrator SSE flow and client state updates, increasing integration risk.

Overview
Persists “open resources” on copilot tasks and makes them user-editable. Chats now store a resources array in copilot_chats and /api/copilot/chat includes it in GET responses.

Adds a dedicated /api/copilot/chat/resources endpoint to add/remove/reorder resources (with basic validation + title de-duplication), plus new React Query mutations to call it.

Updates the task UI to support adding resources via a + dropdown, closing tabs, and drag-and-drop reordering, and refactors resource rendering via a new resource-registry + ResourceActions.

Integrates resource persistence into execution: tool results can yield resources (including via VFS read), the orchestrator persists them and emits a new resource_added SSE event, and the client updates local state + invalidates relevant queries on receipt.

Written by Cursor Bugbot for commit 847c899. This will update automatically on new commits. Configure here.

error: err instanceof Error ? err.message : String(err),
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition in read-modify-write resource persistence

Medium Severity

persistChatResources performs a non-atomic read-modify-write (SELECT then UPDATE) on the resources JSONB column. It's called fire-and-forget via .catch() from the tool execution handler. If two tool results complete in quick succession, the second persist can start its SELECT before the first's UPDATE completes, causing the first resource addition to be silently lost from the database.

Additional Locations (1)
Fix in Cursor Fix in Web

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR replaces the previous heuristic client-side resource extraction (parsing SSE payloads) with authoritative server-side persistence: tool results now emit resource_added SSE events and write to a new resources JSONB column on copilotChats. Users can also manually add, remove, and reorder resources via a new REST API (/api/copilot/chat/resources) and a redesigned ResourceTabs with drag-and-drop, close buttons, and an AddResourceDropdown.

Key observations:

  • The new persistChatResources utility omits updatedAt when writing to the DB, inconsistent with the three API route handlers that all set it explicitly.
  • The VALID_RESOURCE_TYPES.has(resource.type) guard in the POST route is dead code — Zod's z.enum(...) already guarantees the value before it is reached.
  • renderActions is defined in ResourceTypeConfig (and implemented for workflow/knowledgebase) but is never called from MothershipView or any other consumer — both the interface field and its implementations (WorkflowActions, KnowledgeBaseActions) are unreachable.
  • Auto-scroll during tab drag-and-drop duplicates requestAnimationFrame logic across the tab onDragOver handler and the container onDragOver; due to event bubbling, the container handler fires second and cancels the RAF started by the tab handler, which may prevent scrolling from working correctly in edge zones.
  • The onKeyDown close button handler casts React.KeyboardEvent to React.MouseEvent via as unknown as, which is type-unsafe.
  • A database migration file for the new resources column is not included in this PR.

Confidence Score: 3/5

  • This WIP PR has several correctness issues (missing updatedAt, broken auto-scroll RAF, unreachable code paths) and a missing DB migration that should be resolved before merging.
  • The core data flow — server-side extraction → SSE event → client state → DB persistence — is architecturally sound and well-tested with optimistic updates. However, the missing updatedAt write, the dead renderActions registry code that duplicates live components, the RAF clobbering bug in drag-and-drop, and the absence of a DB migration each represent production risks that reduce confidence below a "ready to merge" threshold.
  • apps/sim/lib/copilot/resources.ts (missing updatedAt), apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx (RAF/drag-and-drop), apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry.tsx (dead renderActions), packages/db/schema.ts (migration needed)

Important Files Changed

Filename Overview
apps/sim/lib/copilot/resources.ts New server-side resource extraction and persistence utility — missing updatedAt on DB write inconsistent with API route handlers; internal try/catch silently suppresses all errors making the outer .catch() in tool-execution.ts dead code.
apps/sim/app/api/copilot/chat/resources/route.ts New REST API for add/remove/reorder of chat resources — well-structured with proper auth, Zod validation, and optimistic rollback; minor redundant runtime type check after Zod already enforces the enum.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx Major overhaul adding drag-and-drop reordering, close buttons, and an AddResourceDropdown — auto-scroll RAF logic is duplicated and can be clobbered by event bubbling; keyboard handler uses an unsafe type cast.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry.tsx New resource type registry combining config and component rendering — renderActions field is defined for workflow/knowledgebase entries but never called from any consumer, making those implementations dead code.
packages/db/schema.ts Adds resources jsonb NOT NULL DEFAULT '[]' column to copilotChats — schema change is correct; a corresponding database migration file does not appear to be included in the PR.
apps/sim/lib/copilot/orchestrator/sse/handlers/tool-execution.ts Emits resource_added SSE events and fires-and-forgets persistChatResources after each successful tool execution — outer .catch() is unreachable because the callee already swallows all errors internally.

Sequence Diagram

sequenceDiagram
    participant Client as Browser (useChat)
    participant SSE as SSE Stream
    participant ToolExec as tool-execution.ts
    participant DB as Database (copilotChats)
    participant API as /api/copilot/chat/resources

    Client->>SSE: sendMessage()
    SSE->>ToolExec: executeToolAndReport()
    ToolExec->>ToolExec: extractResourcesFromToolResult()
    ToolExec-->>DB: persistChatResources() [fire-and-forget]
    ToolExec-->>SSE: emit resource_added event
    SSE-->>Client: { type: 'resource_added', resource }
    Client->>Client: addResource() → setResources()

    Note over Client,DB: User manually adds/removes/reorders

    Client->>API: POST /resources (add)
    API->>DB: UPDATE copilotChats SET resources = merged
    API-->>Client: { success, resources }

    Client->>API: DELETE /resources (remove)
    API->>DB: UPDATE copilotChats SET resources = filtered
    API-->>Client: { success, resources }

    Client->>API: PATCH /resources (reorder)
    API->>DB: UPDATE copilotChats SET resources = newOrder
    API-->>Client: { success, resources }

    Note over Client,DB: On page reload
    Client->>API: GET /api/copilot/chat?chatId=...
    API->>DB: SELECT resources FROM copilotChats
    DB-->>API: resources[]
    API-->>Client: chatHistory.resources
    Client->>Client: setResources(chatHistory.resources)
Loading

Comments Outside Diff (4)

  1. apps/sim/lib/copilot/orchestrator/sse/handlers/tool-execution.ts, line 2501-2506 (link)

    .catch() handler is unreachable — double-logging dead code

    persistChatResources already wraps its entire body in a try/catch that logs the error and returns (never rejects). The .catch() attached here will therefore never be invoked, creating dead code. If you want the outer call-site to observe failures, the inner function should re-throw (or not catch at all); otherwise, remove the outer .catch() to avoid the illusion of independent error handling.

  2. apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry.tsx, line 353-354 (link)

    renderActions is dead code — never called from MothershipView

    The ResourceTypeConfig interface and both workflow / knowledgebase entries define renderActions, but mothership-view.tsx (the sole consumer of the registry) passes actions to ResourceTabs using ResourceActions from resource-content.tsx — it never calls getResourceConfig(type).renderActions. This property, its implementations (WorkflowActions, KnowledgeBaseActions), and the renderActions function types are all unreachable. Either wire them up to replace the existing ResourceActions dispatch in mothership-view.tsx, or remove them to avoid maintaining unused code paths.

  3. apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx, line 868-891 (link)

    Auto-scroll RAF is clobbered by event bubbling

    handleDragOver (tab-level) and the container div's onDragOver both run on every dragover event because the tab event bubbles to the container. The execution order is:

    1. Tab's handleDragOver — cancels existing RAF, starts a new scroll loop, updates dropGapIdx.
    2. Container's onDragOverimmediately cancels the RAF from step 1 and starts yet another one (or calls stopAutoScroll() when not in the edge zone).

    When the cursor is not in the edge zone, the container's else branch calls stopAutoScroll() which cancels any RAF started in step 1 even if the tab computed a scroll direction. This means auto-scroll may never actually run during normal dragging. Consider either using e.stopPropagation() in handleDragOver to prevent the tab event from reaching the container, or centralising all auto-scroll logic in the container handler only.

  4. apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx, line 940 (link)

    Unsafe KeyboardEvent cast to MouseEvent in close button

    handleRemove is typed to accept React.MouseEvent, but the onKeyDown handler casts a React.KeyboardEvent to it using as unknown as React.MouseEvent. Accessing any mouse-specific property inside handleRemove (e.g. clientX, button) in the future would silently operate on undefined. Use e.stopPropagation() directly in the keyboard handler and call onRemoveResource directly instead of routing through a mouse handler.

Last reviewed commit: 544af06

Comment on lines +124 to +127
return []
}

case 'knowledge': {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing updatedAt on persistence write

persistChatResources updates the resources column but omits updatedAt, making it inconsistent with all three handlers in route.ts (POST/PATCH/DELETE) which explicitly set updatedAt: new Date(). Any updatedAt-based ordering or staleness logic will not reflect server-side resource mutations.

Suggested change
return []
}
case 'knowledge': {
await db
.update(copilotChats)
.set({ resources: sql`${JSON.stringify(merged)}::jsonb`, updatedAt: new Date() })
.where(eq(copilotChats.id, chatId))

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@TheodoreSpeaks TheodoreSpeaks merged commit 295978a into feat/mothership-copilot Mar 13, 2026
4 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/persist-resources branch March 13, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant